{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "gpuType": "T4",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/DrewThomasson/ebook2audiobook/blob/v25/Notebooks/colab_ebook2audiobook.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Welcome to the ebook2audiobook free google colab!\n",
        "## 🌟 Features\n",
        "\n",
        "- 📖 Converts eBooks to text format with Calibre.\n",
        "- 📚 Splits eBook into chapters for organized audio.\n",
        "- 🎙️ High-quality text-to-speech with [Coqui XTTS](https://huggingface.co/coqui/XTTS-v2), [Fairseq](https://github.com/facebookresearch/fairseq), [Bark](https://github.com/suno-ai/bark), [Vits](https://github.com/jaywalnut310/vits), and [Yourtts](https://github.com/Edresson/YourTTS).\n",
        "- 🗣️ Optional voice cloning with your own voice file or choose from our pre-made fine-tuned models!\n",
        "- 🌍 Supports multiple languages! Arabic (ar), Chinese (zh-cn), Czech (cs), Dutch (nl), English (en), French (fr), German (de), Hindi (hi), Hungarian (hu), Italian (it), Japanese (ja), Korean (ko), Polish (pl), Portuguese (pt), Russian (ru), Spanish (es), Turkish (tr), Vietnamese (vi), [+ 1107 languages via Fairseq](https://dl.fbaipublicfiles.com/mms/tts/all-tts-languages.html)\n",
        "## Want to run locally for free? ⬇\n",
        "## [Check out the ebook2audiobook github!](https://github.com/DrewThomasson/ebook2audiobook)"
      ],
      "metadata": {
        "id": "DKNNnwD-HJwQ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# @title 🚀 Run ebook2audiobook!\n",
        "\n",
        "import os\n",
        "import re\n",
        "import subprocess\n",
        "import time  # Import the time module\n",
        "from IPython.display import HTML, display\n",
        "import pkg_resources  # For checking installed packages\n",
        "\n",
        "# Emojis for logs\n",
        "CHECK_MARK = \"✅\"\n",
        "CROSS_MARK = \"❌\"\n",
        "\n",
        "def display_loading_bar(total_steps):\n",
        "    \"\"\"Displays a more visual text-based loading indicator in Colab.\"\"\"\n",
        "    print(\"\\n---  LOADING...  Total steps:\", total_steps, \" ---\") # Separator and loading start\n",
        "\n",
        "def update_progress(step, total_steps):\n",
        "  \"\"\"Updates the text-based loading indicator with a bar-like style.\"\"\"\n",
        "  bar_length = 20  # Length of the \"bar\"\n",
        "  progress_percent = int((step / total_steps) * 100)\n",
        "  progress_filled = int(bar_length * step / total_steps)\n",
        "  bar = '=' * progress_filled + '>' + ' ' * (bar_length - progress_filled -1 if (bar_length - progress_filled -1) >= 0 else 0) # Handle potential index error\n",
        "  progress_text = f\"[{bar}] {progress_percent}% ({step}/{total_steps})\"\n",
        "  print(f\"---  PROGRESS:  {progress_text} ---\") # Separator for progress update\n",
        "\n",
        "def run_command_with_log(command, description, step_progress, total_step_commands):\n",
        "    \"\"\"Runs a command and logs its progress and outcome with emojis and duration.\"\"\"\n",
        "    print(f\"\\n{step_progress}/{total_step_commands}: {description}...\")\n",
        "    start_time = time.time()  # Record start time\n",
        "    try:\n",
        "        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
        "        stdout, stderr = process.communicate()\n",
        "        end_time = time.time()  # Record end time\n",
        "        duration = end_time - start_time # Calculate duration\n",
        "        duration_formatted = \"{:.2f}\".format(duration) # Format duration to 2 decimal places\n",
        "\n",
        "        if process.returncode != 0:\n",
        "            print(f\"{CROSS_MARK} Command failed: {description} (Took {duration_formatted} seconds)\")\n",
        "            print(f\"   Command: {command}\")\n",
        "            print(f\"   Error Output:\\n{stderr.decode()}\")\n",
        "            return False\n",
        "        else:\n",
        "            update_progress(step_progress, total_step_commands) # Use updated progress function\n",
        "            print(f\"{CHECK_MARK} Command {step_progress}/{total_step_commands} completed: {description} (Took {duration_formatted} seconds)\")\n",
        "            return True\n",
        "\n",
        "    except Exception as e:\n",
        "        end_time = time.time()  # Record end time even if exception occurs\n",
        "        duration = end_time - start_time # Calculate duration\n",
        "        duration_formatted = \"{:.2f}\".format(duration) # Format duration\n",
        "        print(f\"{CROSS_MARK} A general error occurred during command: {description} (Took {duration_formatted} seconds)\")\n",
        "        print(f\"   Error: {e}\")\n",
        "        return False\n",
        "\n",
        "# --- Step 1: OS-Level Installations ---\n",
        "os_install_commands = [\n",
        "    (\"sudo apt-get update\", \"Updating software package lists (Takes around 14.14 seconds)\"),\n",
        "    (\"apt-get install -y libxcb-cursor0\", \"Install cursor display library (libxcb-cursor0 - for Calibre) (Takes around 12.99 seconds)\"),\n",
        "    (\"sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin\", \"Download & install Calibre ebook manager(Takes around 29.40 seconds)\"),\n",
        "    (\"apt-get install -y libegl1\", \"Install graphics library (libegl1) (Takes around 4.12 seconds)\"),\n",
        "    (\"apt-get install -y libopengl0\", \"Install graphics library (libopengl0) (Takes around 3.07 seconds)\"),\n",
        "    (\"apt-get install -y ffmpeg\", \"Install ffmpeg (audio/video conversion) (Takes around 3.52 seconds)\"),\n",
        "    (\"apt-get install -y espeak-ng\", \"Install espeak-ng (espeak-ng tts engine) (Takes around 3.52 seconds)\"),\n",
        "    (\"apt-get install -y mecab\", \"Install mecab (Japanese text analysis) (Takes around 14.51 seconds)\"),\n",
        "    (\"apt-get install -y libmecab-dev\", \"Install mecab development files (Takes around 5.72 seconds)\"),\n",
        "    (\"apt-get install -y mecab-ipadic-utf8\", \"Install mecab Japanese dictionary (UTF-8) (Takes around 9.58 seconds)\"),\n",
        "    (\"apt-get install -y nodejs\", \"Install nodejs (Javascript runtime) (Takes around 7.03 seconds)\"),\n",
        "    (\"pip install mecab-python3 unidic-lite\", \"Install mecab-python3 (Takes around 19.80 seconds)\"),\n",
        "    (\"pip install unidic-lite\", \"Install lightweight unidic dictionary (Takes around 2.15 seconds)\"),\n",
        "    (\"pip install unidic\", \"Install full Unidic dictionary (Takes around 4.00 seconds)\"),\n",
        "    (\"python -m unidic download\", \"Download Unidic dictionary data (Takes around 29.05 seconds)\"),\n",
        "]\n",
        "os_install_completed = False\n",
        "\n",
        "# --- Step 2: Git Clone & Requirements ---\n",
        "git_requirements_commands = [\n",
        "    (\"git clone https://github.com/DrewThomasson/ebook2audiobook.git\", \"Git clone Ebook2audiobook\"),\n",
        "    (\"pip install -r /content/ebook2audiobook/requirements.txt\", \"Install Python requirements (Takes around 202.4 seconds)\"),\n",
        "]\n",
        "git_requirements_completed = False\n",
        "\n",
        "# --- Step 3: Run App ---\n",
        "run_app_completed = False\n",
        "\n",
        "\n",
        "# --- Step 1 Execution ---\n",
        "if os_install_completed:\n",
        "    print(\"\\nStep 1: OS-Level Installations - Already completed, skipping.\")\n",
        "else:\n",
        "    print(\"\\n--- Step 1: OS-Level Installations ---\")\n",
        "    print(\"This step installs essential software on the Colab system. (Takes around 3-4 minutes)\")\n",
        "    total_steps_step1 = len(os_install_commands)\n",
        "    display_loading_bar(total_steps_step1) # Start enhanced loading indicator\n",
        "\n",
        "    step1_success = True\n",
        "    for i, (command, description) in enumerate(os_install_commands):\n",
        "        if not run_command_with_log(command, description, i + 1, total_steps_step1):\n",
        "            step1_success = False\n",
        "            break\n",
        "\n",
        "    if step1_success:\n",
        "        os_install_completed = True\n",
        "        print(f\"\\n{CHECK_MARK} Step 1: OS-Level Installations - Completed Successfully!\")\n",
        "    else:\n",
        "        print(f\"\\n{CROSS_MARK} Step 1: OS-Level Installations - Failed. See error messages above.\")\n",
        "\n",
        "\n",
        "# --- Step 2 Execution ---\n",
        "if git_requirements_completed:\n",
        "    print(\"\\nStep 2: Git Clone & Requirements - Already completed, skipping.\")\n",
        "else:\n",
        "    print(\"\\n--- Step 2: Git Clone & Requirements ---\")\n",
        "    print(\"This step downloads program files and installs Python components. (Takes around 3-4 minutes)\")\n",
        "    total_steps_step2 = len(git_requirements_commands)\n",
        "    display_loading_bar(total_steps_step2) # Start enhanced loading indicator\n",
        "\n",
        "    step2_success = True\n",
        "    for i, (command, description) in enumerate(git_requirements_commands):\n",
        "        if not run_command_with_log(command, description, i + 1, total_steps_step2):\n",
        "            step2_success = False\n",
        "            break\n",
        "\n",
        "    if step2_success:\n",
        "        git_requirements_completed = True\n",
        "        print(f\"\\n{CHECK_MARK} Step 2: Git Clone & Requirements - Completed Successfully!\")\n",
        "    else:\n",
        "        print(f\"\\n{CROSS_MARK} Step 2: Git Clone & Requirements - Failed. See error messages above.\")\n",
        "\n",
        "\n",
        "def modify_python_version(filepath, new_version=(3, 11)):\n",
        "    \"\"\"\n",
        "    Modifies the min_python_version in a Python file.\n",
        "    (Function definition - same as before)\n",
        "    \"\"\"\n",
        "    try:\n",
        "        with open(filepath, 'r') as f:\n",
        "            file_content = f.read()\n",
        "        pattern = r\"min_python_version = \\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)\"\n",
        "        def replacement(match):\n",
        "            return f\"min_python_version = ({new_version[0]}, {new_version[1]})\"\n",
        "        new_content = re.sub(pattern, replacement, file_content)\n",
        "        if new_content != file_content:\n",
        "            with open(filepath, 'w') as f:\n",
        "                f.write(new_content)\n",
        "            print(f\"Successfully changed min_python_version in {filepath} to {new_version}\")\n",
        "        else:\n",
        "            print(f\"min_python_version in {filepath} was already {new_version} or not found.\")\n",
        "    except FileNotFoundError:\n",
        "        print(f\"Error: File not found: {filepath}\")\n",
        "    except Exception as e:\n",
        "        print(f\"An error occurred: {e}\")\n",
        "\n",
        "def fix_functions_py(filepath):\n",
        "    \"\"\"\n",
        "    Scans functions.py and fixes the incorrect f-string issue if found.\n",
        "    \"\"\"\n",
        "    # Define the incorrect pattern (allowing indentation and triple quotes)\n",
        "    incorrect_pattern = r\"msg\\s*=\\s*f'Voice file \\{re\\.sub\\(r'_\\(24000\\|16000\\)\\\\.wav\\$', '', selected_name\\)\\} deleted!'\"\n",
        "\n",
        "    # Correct replacement (keeping indentation)\n",
        "    correct_replacement = \"\"\"                        cleaned_name = re.sub(r'_(24000|16000)\\\\.wav$', '', selected_name)\n",
        "                        msg = f\"Voice file {cleaned_name} deleted!\" \"\"\"\n",
        "\n",
        "    try:\n",
        "        # Read the file\n",
        "        with open(filepath, \"r\") as file:\n",
        "            lines = file.readlines()\n",
        "\n",
        "        # Check and replace the incorrect line\n",
        "        modified_lines = []\n",
        "        fixed = False\n",
        "\n",
        "        for line in lines:\n",
        "            if re.search(incorrect_pattern, line.strip()):  # Use regex search instead of exact match\n",
        "                print(\"🔍 Found incorrect line in functions.py. Fixing it now...\")\n",
        "                modified_lines.append(correct_replacement + \"\\n\")  # Add the corrected version\n",
        "                fixed = True\n",
        "            else:\n",
        "                modified_lines.append(line)  # Keep other lines unchanged\n",
        "\n",
        "        # Only overwrite the file if a fix was applied\n",
        "        if fixed:\n",
        "            with open(filepath, \"w\") as file:\n",
        "                file.writelines(modified_lines)\n",
        "            print(\"✅ Fix applied successfully!\")\n",
        "        else:\n",
        "            print(\"✅ No fix needed. The exact error line was not found.\")\n",
        "\n",
        "    except FileNotFoundError:\n",
        "        print(f\"❌ Error: The file {filepath} was not found.\")\n",
        "    except Exception as e:\n",
        "        print(f\"❌ An error occurred: {e}\")\n",
        "\n",
        "# Modify conf.py AFTER git clone (Step 2 ensures clone is done)\n",
        "filepath = \"/content/ebook2audiobook/lib/conf.py\"\n",
        "modify_python_version(filepath)\n",
        "\n",
        "# Call the function to fix functions.py\n",
        "filepath = \"/content/ebook2audiobook/lib/functions.py\"\n",
        "fix_functions_py(filepath)\n",
        "\n",
        "\n",
        "\n",
        "\n",
        "# --- Step 3 Execution ---\n",
        "if run_app_completed:\n",
        "    print(\"\\nStep 3: Run App - Already run (or skipped).\")\n",
        "else:\n",
        "    print(\"\\n--- Step 3: Run App ---\")\n",
        "    print(\"This step starts the ebook2audiobook web interface.\")\n",
        "    print(\"Starting ebook2audiobook...\")\n",
        "    try:\n",
        "        !cd ebook2audiobook && python app.py --script_mode full_docker --share\n",
        "        run_app_completed = True\n",
        "    except Exception as e:\n",
        "        print(f\"{CROSS_MARK} Error starting app.py: {e}\")\n",
        "    else:\n",
        "        print(f\"\\n{CHECK_MARK} Step 3: Run App - Started! Check the output above for the Gradio link.\")\n",
        "\n",
        "\n",
        "print(\"\\n--- All Steps Initiated (or Skipped if Already Run) ---\")\n",
        "print(\"Check the output above for details on each step.\")"
      ],
      "metadata": {
        "id": "Edxj355K0rUz",
        "collapsed": true,
        "cellView": "form"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}